package com.tddinaction.patterns.test;

import java.lang.reflect.*;

/**
 * a.k.a. The "ObjectMolester"
 * <p>
 * This class is used to access a method or field of an object no matter what
 * the access modifier of the method or field. The syntax for accessing fields
 * and methods is out of the ordinary because this class uses reflection to peel
 * away protection.
 * <p>
 * Here is an example of using this to access a private member.
 * <code>resolveName</code> is a private method of <code>Class</code>.
 * 
 * <pre>
 * Class c = Class.class;
 * System.out.println(PrivilegedAccessor.invokeMethod(c, &quot;resolveName&quot;,
 *         &quot;/net/iss/common/PrivilegeAccessor&quot;));
 * </pre>
 * 
 * @author Charlie Hubbard (chubbard@iss.net)
 * @author Prashant Dhokte (pdhokte@iss.net)
 */
public class PrivilegedAccessor {

    /**
     * Gets the value of the named field and returns it as an object.
     * 
     * @param instance
     *            the object instance
     * @param fieldName
     *            the name of the field
     * @return an object representing the value of the field
     */
    public static Object getValue(Object instance, String fieldName)
            throws IllegalAccessException, NoSuchFieldException {
        Field field = getField(instance.getClass(), fieldName);
        field.setAccessible(true);
        return field.get(instance);
    }

    /**
     * Calls a method on the given object instance with the given argument.
     * 
     * @param instance
     *            the object instance
     * @param methodName
     *            the name of the method to invoke
     * @param arg
     *            the argument to pass to the method
     * @see PrivilegedAccessor#invokeMethod(Object,String,Object[])
     */
    public static Object invokeMethod(Object instance,
            String methodName, Object arg)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        Object[] args = new Object[1];
        args[0] = arg;
        return invokeMethod(instance, methodName, args);
    }

    /**
     * Calls a method on the given object instance with the given arguments.
     * 
     * @param instance
     *            the object instance
     * @param methodName
     *            the name of the method to invoke
     * @param args
     *            an array of objects to pass as arguments
     * @see PrivilegedAccessor#invokeMethod(Object,String,Object)
     */
    public static Object invokeMethod(Object instance,
            String methodName, Object[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        Class[] classTypes = null;
        if (args != null) {
            classTypes = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
                if (args[i] != null)
                    classTypes[i] = args[i].getClass();
            }
        }
        return getMethod(instance, methodName, classTypes).invoke(
                instance, args);
    }

    /**
     * 
     * @param instance
     *            the object instance
     * @param methodName
     *            the
     */
    public static Method getMethod(Object instance,
            String methodName, Class[] classTypes)
            throws NoSuchMethodException {
        Method accessMethod = getMethod(instance.getClass(),
                methodName, classTypes);
        accessMethod.setAccessible(true);
        return accessMethod;
    }

    /**
     * Return the named field from the given class.
     */
    private static Field getField(Class thisClass, String fieldName)
            throws NoSuchFieldException {
        if (thisClass == null)
            throw new NoSuchFieldException("Invalid field : "
                    + fieldName);
        try {
            return thisClass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            return getField(thisClass.getSuperclass(), fieldName);
        }
    }

    /**
     * Return the named method with a method signature matching classTypes from
     * the given class.
     */
    private static Method getMethod(Class thisClass,
            String methodName, Class[] classTypes)
            throws NoSuchMethodException {
        if (thisClass == null)
            throw new NoSuchMethodException("Invalid method : "
                    + methodName);
        try {
            return thisClass
                    .getDeclaredMethod(methodName, classTypes);
        } catch (NoSuchMethodException e) {
            return getMethod(thisClass.getSuperclass(), methodName,
                    classTypes);
        }
    }
}